#define use_threadpool
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Threading;
using System.Collections.Specialized;
using System.Diagnostics;

namespace PocketLibrary.Storage
{
    public class BoxNetAPI
    {
        private string apiKey = "qxdm1pda0a35krov74h3ox0ckzogzrq0";
        private string ticket = "";
        private string authToken = "";
        private string apiEndpoint = "http://www.box.net/api/1.0/rest";
        private string uploadEndpoint = "http://upload.box.net/api/1.0/upload/";
        private string sid = "";
        private string pFileId = "";
        private string pDownloadPath = "";
        private string pUploadPath = "";
        private string UploadPath
        {
            get { return pUploadPath; }
        }
        private Stack<string> breadCrumbs = new Stack<string>();

        // threadpool variables
        private AutoResetEvent doneEvent;

        private string pUsername;
        public string Username
        {
            get { return pUsername; }
            set { pUsername = value; }
        }
        private string pPassword;
        public string Password
        {
            get { return pPassword; }
            set { pPassword = value; }
        }

        #region events
        public event BoxNetApiTreeHandler OnTreeResponse;
        private void TreeResponse(BoxNetApiTreeArgs args)
        {
            if (OnTreeResponse != null)
                OnTreeResponse(this, args);
        }

        public event BoxNetApiResponseHandler OnResponse;
        private void Response(BoxNetApiResponseArgs args)
        {
            if (OnResponse != null)
                OnResponse(this, args);
        }


        public event BoxNetApiResponseHandler OnLogin;
        private void LoginResponse(BoxNetApiResponseArgs args)
        {
            if (OnLogin != null)
                OnLogin(this, args);
        }

        public event BoxNetApiResponseHandler OnDownload;
        private void DownloadComplete()
        {
            if (OnDownload != null)
            {
                BoxNetApiResponseArgs args = new BoxNetApiResponseArgs();
                args.Response = "file downloaded";
                OnDownload(this, args);
            }
        }

        private void DownloadFailed()
        {
            if (OnDownload != null)
            {
                BoxNetApiResponseArgs args = new BoxNetApiResponseArgs();
                args.Response = "file download failed";
                OnDownload(this, args);
            }
        }

        public event UploadProgressHandler OnDownloadProgress;
        public event UploadProgressHandler OnUploadProgress;

        #endregion

        #region public methods
        public bool Login()
        {
            return this.authorization();
        }

        public bool Login(string user, string pass)
        {
            this.Username = user;
            this.Password = pass;
            return this.authorization();
        }

        public bool ListFolder()
        {
            if (breadCrumbs.Count == 0)
            {
                breadCrumbs.Push("0");
                return this.get_account_tree();
            }
            else
                return this.get_account_tree(breadCrumbs.Peek());
        }

        public bool ListFolder(string folder)
        {
            breadCrumbs.Push(folder);

            return this.get_account_tree(folder);
        }

        public bool ListParentFolder()
        {
            //if (breadCrumbs.Peek() != "0")
            //    return this.get_account_tree(breadCrumbs.Pop(), true);
            //else

            // get rid of string on top of stack
            breadCrumbs.Pop();
            return this.get_account_tree(breadCrumbs.Peek());
        }

        public void Download(string file_id, string targetPath)
        {
            pFileId = file_id;
            pDownloadPath = targetPath;
#if use_threadpool
            doneEvent = new AutoResetEvent(false);
            ThreadPool.QueueUserWorkItem(new WaitCallback(boxDownloadFileThreadPool));
#else
            Thread downloadThread = new Thread(boxDownloadFile);
            downloadThread.IsBackground = true;
            downloadThread.Start();
#endif
        }

        int pFileSize = 0;
        public void Download(string file_id, int filesize, string targetPath)
        {
            pFileId = file_id;
            pDownloadPath = targetPath;
            pFileSize = filesize;
#if use_threadpool
            doneEvent = new AutoResetEvent(false);
            ThreadPool.QueueUserWorkItem(new WaitCallback(boxDownloadFileThreadPool));
#else
            Thread downloadThread = new Thread(boxDownloadFile);
            downloadThread.IsBackground = true;
            downloadThread.Start();
#endif
        }

        public bool Delete(BoxNetAPITreeItem.TreeItemType type, string itemid)
        {
            return boxDeleteFile(type, itemid);
        }



        public void Upload(string filepath)
        {
#if use_threadpool
            pUploadPath = filepath;
            ThreadPool.QueueUserWorkItem(new WaitCallback(boxUploadThreadPool));

#else
            boxUpload(filepath);
#endif
        }

        #endregion


        #region private methods
        private bool get_ticket()
        {

            StringBuilder requestString = new StringBuilder();
            requestString.Append("?action=get_ticket&api_key=");
            requestString.Append(apiKey);

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiEndpoint + requestString);

            request.Method = "GET";
            request.UserAgent = "TaskuBlogi 1.2";

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            StringBuilder body = new StringBuilder();

            Stream stream = response.GetResponseStream();

            StreamReader reader = new StreamReader(stream, Encoding.UTF8);

            body.Append(reader.ReadToEnd());

            stream.Close();

            XmlDocument responseXml = new XmlDocument();
            responseXml.Load(body.ToString());

            XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");
            if (statusnode != null || statusnode.InnerText == "get_ticket_ok")
            {
                XmlNode ticketnode = responseXml.SelectSingleNode("descendant::response/ticket");
                this.ticket = ticketnode.InnerText;
                return true;
            }
            else
                return false;
        }

        private bool get_authtoken()
        {
            StringBuilder requestString = new StringBuilder();
            requestString.Append("?action=get_auth_token&api_key=");
            requestString.Append(apiKey);
            requestString.Append("&ticket=");
            requestString.Append(ticket);

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiEndpoint + requestString);

            request.Method = "GET";
            request.UserAgent = "TaskuBlogi 1.2";

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            StringBuilder body = new StringBuilder();

            Stream stream = response.GetResponseStream();

            StreamReader reader = new StreamReader(stream, Encoding.UTF8);

            body.Append(reader.ReadToEnd());

            stream.Close();

            XmlDocument responseXml = new XmlDocument();
            responseXml.Load(body.ToString());

            XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");
            if (statusnode != null || statusnode.InnerText == "get_auth_token_ok")
            {
                XmlNode tokennode = responseXml.SelectSingleNode("descendant::response/auth_token");
                this.authToken = tokennode.InnerText;
                return true;
            }
            else
                return false;
        }

        private bool authorization()
        {
            try
            {
                StringBuilder requestString = new StringBuilder();
                requestString.Append("?action=authorization&api_key=");
                requestString.Append(apiKey);
                requestString.Append("&login=");
                requestString.Append(this.Username);
                requestString.Append("&password=");
                requestString.Append(HelperUtils.MD5String(this.Username + this.Password));
                requestString.Append("&method=md5");

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiEndpoint + requestString);

                request.Method = "GET";
                request.UserAgent = "TaskuBlogi 1.2";

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                StringBuilder body = new StringBuilder();

                Stream stream = response.GetResponseStream();

                StreamReader reader = new StreamReader(stream, Encoding.UTF8);

                body.Append(reader.ReadToEnd());

                stream.Close();

                XmlDocument responseXml = new XmlDocument();
                responseXml.LoadXml(body.ToString());

                XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");

                if (statusnode != null && statusnode.InnerText == "logged")
                {
                    XmlNode tokennode = responseXml.SelectSingleNode("descendant::response/auth_token");
                    this.authToken = tokennode.InnerText;

                    BoxNetApiResponseArgs args = new BoxNetApiResponseArgs();
                    args.Response = "logged";
                    if (OnLogin != null)
                        OnLogin(this, args);
                    return true;
                }
                else
                {
                    BoxNetApiResponseArgs args = new BoxNetApiResponseArgs();
                    args.Response = "not logged";
                    if (OnLogin != null)
                        OnLogin(this, args);
                    return false;
                }
            }
            catch (WebException)
            {
                return false;
            }
        }

        private bool get_account_tree()
        {
            return get_account_tree("0");
        }

        private bool get_account_tree(string folder)
        {
            try
            {
                StringBuilder requestString = new StringBuilder();
                requestString.Append("?action=get_account_tree&api_key=");
                requestString.Append(apiKey);
                requestString.Append("&auth_token=");
                requestString.Append(authToken);
                requestString.Append("&folder_id=");
                requestString.Append(folder);
                requestString.Append("&params[]=nozip");
                requestString.Append("&params[]=onelevel");

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiEndpoint + requestString);

                request.Method = "GET";
                request.UserAgent = "TaskuBlogi 1.2";

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                StringBuilder body = new StringBuilder();

                Stream stream = response.GetResponseStream();

                StreamReader reader = new StreamReader(stream, Encoding.UTF8);

                body.Append(reader.ReadToEnd());

                stream.Close();

                XmlDocument responseXml = new XmlDocument();
                responseXml.LoadXml(body.ToString());

                XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");
                if (statusnode != null || statusnode.InnerText == "listing_ok")
                {
                    BoxNetApiTreeArgs args = new BoxNetApiTreeArgs();

                    //XmlNodeList tagnodelist = 
                    //    responseXml.SelectNodes("descendant::response/tree/folder/tags");

                    XmlNodeList foldernodelist =
                        responseXml.SelectNodes("descendant::response/tree/folder/folders/folder");

                    string stackPeek = breadCrumbs.Peek();
                    if (stackPeek != "0")
                    {
                        BoxNetAPITreeItem parentitem =
                            new BoxNetAPITreeItem(
                            "..",
                            stackPeek,
                            BoxNetAPITreeItem.TreeItemType.Parent);
                        args.TreeItems.Add(parentitem);
                    }

                    for (int i = 0; i < foldernodelist.Count; i++)
                    {
                        BoxNetAPITreeItem item =
                            new BoxNetAPITreeItem(
                            foldernodelist[i].Attributes["name"].InnerText,
                            foldernodelist[i].Attributes["id"].InnerText,
                            BoxNetAPITreeItem.TreeItemType.Folder);
                        args.TreeItems.Add(item);
                    }


                    XmlNodeList filenodelist =
                        responseXml.SelectNodes("descendant::response/tree/folder/files/file");

                    for (int i = 0; i < filenodelist.Count; i++)
                    {
                        BoxNetAPITreeItem item =
                            new BoxNetAPITreeItem(
                            filenodelist[i].Attributes["file_name"].InnerText,
                            filenodelist[i].Attributes["id"].InnerText,
                            filenodelist[i].Attributes["size"].InnerText,
                            BoxNetAPITreeItem.TreeItemType.File);
                        args.TreeItems.Add(item);
                    }

                    if (OnTreeResponse != null)
                        OnTreeResponse(this, args);

                    return true;
                }
                else
                    return false;
            }
            catch (WebException)
            {
                return false;
            }


        }

        private void boxDownloadFile()
        {
            try
            {
                StringBuilder requestString = new StringBuilder();
                requestString.Append("http://box.net/api/1.0/download/");
                requestString.Append(authToken + "/" + pFileId);
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestString.ToString());
                req.Method = "GET";
                req.UserAgent = "TaskuBlogi 1.2";

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

                // Retrieve response stream
                Stream respStream = resp.GetResponseStream();

                // Create local file
                FileStream wrtr = new FileStream(pDownloadPath, FileMode.Create);


                // Allocate byte buffer to hold stream contents
                byte[] inData = new byte[4096];

                int transfered = 0;

                int responseLength = (int)resp.ContentLength;

                // loop through response stream reading each data block
                //  and writing to the local file
                int bytesRead = respStream.Read(inData, 0, inData.Length);
                while (bytesRead > 0)
                {
                    transfered += bytesRead;
                    wrtr.Write(inData, 0, bytesRead);
                    bytesRead = respStream.Read(inData, 0, inData.Length);
                    OnDownloadProgress(
                        this, new UploadProgressEventArgs(
                        transfered, responseLength, transfered == responseLength));
                }

                DownloadComplete();

                respStream.Close();
                resp.Close();
                wrtr.Close();
                doneEvent.Set();
            }
            catch (ThreadAbortException)
            {
            }
            catch (WebException)
            {
            }
        }

        private void boxDownloadFileThreadPool(object stateInfo)
        {
            try
            {
                StringBuilder requestString = new StringBuilder();
                requestString.Append("http://box.net/api/1.0/download/");
                requestString.Append(authToken + "/" + pFileId);
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestString.ToString());
                req.Method = "GET";
                req.UserAgent = "TaskuBlogi 1.2";

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

                // Retrieve response stream
                Stream respStream = resp.GetResponseStream();

                // Create local file
                FileStream wrtr = new FileStream(pDownloadPath, FileMode.Create);


                // Allocate byte buffer to hold stream contents
                byte[] inData = new byte[4096];

                int transfered = 0;

                int responseLength = (int)resp.ContentLength;

                // loop through response stream reading each data block
                //  and writing to the local file
                int bytesRead = respStream.Read(inData, 0, inData.Length);
                while (bytesRead > 0)
                {
                    transfered += bytesRead;
                    wrtr.Write(inData, 0, bytesRead);
                    bytesRead = respStream.Read(inData, 0, inData.Length);
                    OnDownloadProgress(
                        this, new UploadProgressEventArgs(
                        transfered, responseLength, transfered == responseLength));
                }

                DownloadComplete();

                respStream.Close();
                resp.Close();
                wrtr.Close();
                doneEvent.Set();
            }
            catch (ThreadAbortException)
            {
            }
            catch (WebException)
            {
                Debug.Write("webexception caught");
            }
        }

        private void boxUpload(string filepath)
        {
            XmlDocument responseXml = new XmlDocument();

            string boundary = "TASKUBLOGI_MIME_" + DateTime.Now.ToString("yyyyMMddhhmmss");
            string filename = filepath.Substring(filepath.LastIndexOf("\\") + 1);
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("new_file_1", filename);
            //parameters.Add("share", "0");

            StringBuilder uploadUrl = new StringBuilder();
            uploadUrl.Append(uploadEndpoint);
            uploadUrl.Append(authToken + "/" + breadCrumbs.Peek());
            Debug.WriteLine(uploadUrl.ToString());
            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uploadUrl.ToString());
            req.Method = "POST";
            req.UserAgent = "TaskuBlogi 1.2";
            req.KeepAlive = false;
            req.Timeout = 50000;
            req.ContentType = "multipart/form-data; boundary=\"" + boundary + "\"";
            //req.UserAgent = UserAgent;

            StringBuilder sb = new StringBuilder();

            string[] keys = parameters.AllKeys;
            //StringBuilder HashStringBuilder = new StringBuilder();
            //Array.Sort(keys);

            foreach (string key in keys)
            {
                //HashStringBuilder.Append(key);
                //HashStringBuilder.Append(parameters[key]);
                sb.Append("--" + boundary + "\r\n");
                sb.Append("Content-Disposition: form-data; name=\"" + key + "\"\r\n");
                sb.Append("\r\n");
                sb.Append(parameters[key] + "\r\n");
            }

            sb.Append("--" + boundary + "\r\n");
            sb.Append("Content-Disposition: form-data; name=\"upload_files\"; filename=\"" + filename + "\"\r\n");
            sb.Append("Content-Type: " + HelperUtils.ResolveMimeType(filename) + "\r\n");
            sb.Append("\r\n");

            UTF8Encoding encoding = new UTF8Encoding();

            byte[] postContents = encoding.GetBytes(sb.ToString());

            FileStream fstream = new FileStream(filepath, FileMode.Open);
            byte[] fileContents = new byte[fstream.Length];
            fstream.Read(fileContents, 0, fileContents.Length);
            fstream.Close();

            byte[] postFooter = encoding.GetBytes("\r\n--" + boundary + "--\r\n");

            byte[] dataBuffer = new byte[postContents.Length + fileContents.Length + postFooter.Length];
            Buffer.BlockCopy(postContents, 0, dataBuffer, 0, postContents.Length);
            Buffer.BlockCopy(fileContents, 0, dataBuffer, postContents.Length, fileContents.Length);
            Buffer.BlockCopy(postFooter, 0, dataBuffer, postContents.Length + fileContents.Length, postFooter.Length);

            req.ContentLength = dataBuffer.Length;
            Stream reqStream = null;
            Stream resStream = null;
            try
            {
                reqStream = req.GetRequestStream();

                int j = 0;
                int uploadBit = Math.Max(dataBuffer.Length / 100, 50 * 1024);
                int uploadSoFar = 0;

                for (int i = 0; i < dataBuffer.Length; i = i + uploadBit)
                {
                    int toUpload = Math.Min(uploadBit, dataBuffer.Length - i);
                    uploadSoFar += toUpload;

                    reqStream.Write(dataBuffer, i, toUpload);

                    //TransferChunk(dataBuffer.Length, i + toUpload);

                    if ((OnUploadProgress != null) && ((j++) % 5 == 0 || uploadSoFar == dataBuffer.Length))
                    {
                        OnUploadProgress(this,
                            new UploadProgressEventArgs(
                            i + toUpload,
                            (int)req.ContentLength,
                            uploadSoFar == dataBuffer.Length));
                    }
                }
                reqStream.Close();
                StringBuilder body = new StringBuilder();
                HttpWebResponse response = (HttpWebResponse)req.GetResponse();
                resStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(resStream, Encoding.UTF8);
                body.Append(reader.ReadToEnd());
                responseXml.LoadXml(body.ToString());
            }
            catch (WebException)
            {
            }
            catch (IOException ex)
            {
                Debug.Write(ex.Message, "boxnet rest upload");
            }
            finally
            {
                resStream.Close();
                XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");
                if (statusnode != null || statusnode.InnerText == "upload_ok")
                {
                    get_account_tree(breadCrumbs.Peek());
                }
            }
        }

        private void boxUploadThreadPool(object stateInfo)
        {
            try
            {
                boxUpload(UploadPath);
            }
            catch (ThreadAbortException)
            {
            }
        }

        private bool boxDeleteFile(BoxNetAPITreeItem.TreeItemType type, string id)
        {
            try
            {
                StringBuilder requestString = new StringBuilder();
                requestString.Append("?action=delete&api_key=");
                requestString.Append(apiKey);
                requestString.Append("&auth_token=");
                requestString.Append(authToken);
                if (type == BoxNetAPITreeItem.TreeItemType.Folder)
                    requestString.Append("&target=folder");
                else if (type == BoxNetAPITreeItem.TreeItemType.File)
                    requestString.Append("&target=file");
                else
                    return false;

                requestString.Append("&target_id=" + id);

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiEndpoint + requestString);

                request.Method = "GET";
                request.UserAgent = "TaskuBlogi 1.2";

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                StringBuilder body = new StringBuilder();

                Stream stream = response.GetResponseStream();

                StreamReader reader = new StreamReader(stream, Encoding.UTF8);

                body.Append(reader.ReadToEnd());

                stream.Close();

                XmlDocument responseXml = new XmlDocument();
                responseXml.LoadXml(body.ToString());

                XmlNode statusnode = responseXml.SelectSingleNode("descendant::response/status");
                if (statusnode != null || statusnode.InnerText == "s_delete_node")
                {
                    get_account_tree(breadCrumbs.Peek());
                    return true;
                }
                else
                    return false;
            }
            catch (WebException)
            {
                return false;
            }

        }

        #endregion
    }

    public class BoxNetAPITreeItem
    {
        public enum TreeItemType
        {
            Folder, File, Parent
        }

        private string pName;

        public string Name
        {
            get { return pName; }
            set { pName = value; }
        }

        private string pId;

        public string ItemId
        {
            get { return pId; }
            set { pId = value; }
        }

        private TreeItemType pType;

        public TreeItemType ItemType
        {
            get { return pType; }
            set { pType = value; }
        }

        private int pSize = 0;
        public int FileSize
        {
            get
            {
                return pSize;
            }
        }


        public BoxNetAPITreeItem(string name, string id, TreeItemType type)
        {
            Name = name;
            ItemId = id;
            ItemType = type;
        }

        public BoxNetAPITreeItem(string name, string id, string size, TreeItemType type)
        {
            Name = name;
            ItemId = id;
            ItemType = type;
            pSize = int.Parse(size);
        }
    }

    public class BoxNetApiResponseArgs : EventArgs
    {
        private string p_Response;

        public string Response
        {
            get { return p_Response; }
            set { p_Response = value; }
        }


        public BoxNetApiResponseArgs()
        {

        }
    }

    public class BoxNetApiTreeArgs : BoxNetApiResponseArgs
    {
        private List<BoxNetAPITreeItem> p_Items = new List<BoxNetAPITreeItem>();

        public List<BoxNetAPITreeItem> TreeItems
        {
            get { return p_Items; }
            set { p_Items = value; }
        }

    }

    public delegate void BoxNetApiResponseHandler(object sender, BoxNetApiResponseArgs args);
    public delegate void BoxNetApiTreeHandler(object sender, BoxNetApiTreeArgs args);

}
